/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007-2008 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.security.ws.client;

import java.util.HashMap;
import java.util.Map;

import org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor;
import org.apache.cxf.interceptor.InterceptorProvider;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.handler.WSHandlerConstants;
import org.springframework.beans.factory.FactoryBean;

/**
 * a factory that creates webservices client.
 * 
 * @param <WEB_SERVICE>
 *          the type of the generated objects
 */
public class CXFWebWebServiceClientFactory<WEB_SERVICE>
    implements FactoryBean
{

  /**
   * the interceptor that add security dependening on the security context.
   */
  private static final SpringSecurityWSS4JOutInterceptor SPRING_SECURITY_WSS4J_OUT_INTERCEPTOR = new SpringSecurityWSS4JOutInterceptor();

  /**
   * the class of the web service to call.
   */
  private Class<WEB_SERVICE> webServiceClass;

  /**
   * the url of the web service to call.
   */
  private String url;

  /**
   * whether to transfer credentials to the remote webservice.
   */
  private boolean transferCredentials = false;

  /**
   * if transferCredentials is <code>false</code> and login is not null, use
   * it as the login to call the remote web service with.
   */
  private String login = null;

  /**
   * the password associated to the login.
   */
  private String password = null;



  /**
   * Return an instance (possibly shared or independent) of the object managed
   * by this factory.
   * <p>
   * As with a {@link org.springframework.beans.factory.BeanFactory}, this
   * allows support for both the Singleton and Prototype design pattern.
   * <p>
   * If this FactoryBean is not fully initialized yet at the time of the call
   * (for example because it is involved in a circular reference), throw a
   * corresponding
   * {@link org.springframework.beans.factory.FactoryBeanNotInitializedException}.
   * <p>
   * As of Spring 2.0, FactoryBeans are allowed to return <code>null</code>
   * objects. The factory will consider this as normal value to be used; it will
   * not throw a FactoryBeanNotInitializedException in this case anymore.
   * FactoryBean implementations are encouraged to throw
   * FactoryBeanNotInitializedException themselves now, as appropriate.
   * 
   * @return an instance of the bean (can be <code>null</code>)
   * @see org.springframework.beans.factory.FactoryBeanNotInitializedException
   */
  @SuppressWarnings("unchecked")
  public WEB_SERVICE getObject()
  {
    JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
    factory.setServiceClass(getWebServiceClass());
    factory.setAddress(getUrl());
    if (isTransferCredentials() || getLogin() != null) {
      addSecurity(factory);
    }
    return (WEB_SERVICE) factory.create();
  }



  /**
   * Adding WS-Security header to an outgoing web service call.
   * 
   * @param factory
   *          the factory to configure
   */
  public void addSecurity(InterceptorProvider factory)
  {
    factory.getOutInterceptors().add(new SAAJOutInterceptor());
    if (!isTransferCredentials()) {

      Map<String, Object> outProperties = new HashMap<String, Object>();
      // Action to perform : user token
      outProperties.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
      // Password type : plain text
      outProperties.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
      // for hashed password use:
      // properties.setProperty(WSHandlerConstants.PASSWORD_TYPE,
      // WSConstants.PW_DIGEST);
      // User name to send
      outProperties.put(WSHandlerConstants.USER, getLogin());
      // Callback used to retrieve password for given user.
      outProperties.put(WSHandlerConstants.PW_CALLBACK_REF, new SimplePasswordCallback(password));

      factory.getOutInterceptors().add(new WSS4JOutInterceptor(outProperties));
    }
    else {
      factory.getOutInterceptors().add(SPRING_SECURITY_WSS4J_OUT_INTERCEPTOR);
    }
  }



  /**
   * Return the type of object that this FactoryBean creates, or
   * <code>null</code> if not known in advance.
   * <p>
   * This allows one to check for specific types of beans without instantiating
   * objects, for example on autowiring.
   * <p>
   * In the case of implementations that are creating a singleton object, this
   * method should try to avoid singleton creation as far as possible; it should
   * rather estimate the type in advance. For prototypes, returning a meaningful
   * type here is advisable too.
   * <p>
   * This method can be called <i>before</i> this FactoryBean has been fully
   * initialized. It must not rely on state created during initialization; of
   * course, it can still use such state if available.
   * <p>
   * <b>NOTE:</b> Autowiring will simply ignore FactoryBeans that return
   * <code>null</code> here. Therefore it is highly recommended to implement
   * this method properly, using the current state of the FactoryBean.
   * 
   * @return the type of object that this FactoryBean creates, or
   *         <code>null</code> if not known at the time of the call
   * @see org.springframework.beans.factory.ListableBeanFactory#getBeansOfType
   */
  public Class<WEB_SERVICE> getObjectType()
  {
    return getWebServiceClass();
  }



  /**
   * Is the object managed by this factory a singleton? That is, will
   * {@link #getObject()} always return the same object (a reference that can be
   * cached)?
   * <p>
   * <b>NOTE:</b> If a FactoryBean indicates to hold a singleton object, the
   * object returned from <code>getObject()</code> might get cached by the
   * owning BeanFactory. Hence, do not return <code>true</code> unless the
   * FactoryBean always exposes the same reference.
   * <p>
   * The singleton status of the FactoryBean itself will generally be provided
   * by the owning BeanFactory; usually, it has to be defined as singleton
   * there.
   * <p>
   * <b>NOTE:</b> This method returning <code>false</code> does not
   * necessarily indicate that returned objects are independent instances. An
   * implementation of the extended
   * {@link org.springframework.beans.factory.SmartFactoryBean} interface may
   * explicitly indicate independent instances through its
   * {@link org.springframework.beans.factory.SmartFactoryBean#isPrototype()}
   * method. Plain {@link FactoryBean} implementations which do not implement
   * this extended interface are simply assumed to always return independent
   * instances if the <code>isSingleton()</code> implementation returns
   * <code>false</code>.
   * 
   * @return whether the exposed object is a singleton
   * @see #getObject()
   * @see org.springframework.beans.factory.SmartFactoryBean#isPrototype()
   */
  public boolean isSingleton()
  {
    return true;
  }



  /**
   * getter for the webServiceClass property.
   * 
   * @return the webServiceClass
   */
  public Class<WEB_SERVICE> getWebServiceClass()
  {
    return webServiceClass;
  }



  /**
   * setter for the webServiceClass property.
   * 
   * @param webServiceClass
   *          the webServiceClass to set
   */
  public void setWebServiceClass(Class<WEB_SERVICE> webServiceClass)
  {
    this.webServiceClass = webServiceClass;
  }



  /**
   * getter for the url property.
   * 
   * @return the url
   */
  public String getUrl()
  {
    return url;
  }



  /**
   * setter for the url property.
   * 
   * @param url
   *          the url to set
   */
  public void setUrl(String url)
  {
    this.url = url;
  }



  /**
   * getter for the transferCredentials property.
   * 
   * @return the transferCredentials
   */
  public boolean isTransferCredentials()
  {
    return transferCredentials;
  }



  /**
   * setter for the transferCredentials property.
   * 
   * @param transferCredentials
   *          the transferCredentials to set
   */
  public void setTransferCredentials(boolean transferCredentials)
  {
    this.transferCredentials = transferCredentials;
  }



  /**
   * getter for the login property.
   * 
   * @return the login
   */
  public String getLogin()
  {
    return login;
  }



  /**
   * setter for the login property.
   * 
   * @param login
   *          the login to set
   */
  public void setLogin(String login)
  {
    this.login = login;
  }



  /**
   * getter for the password property.
   * 
   * @return the password
   */
  public String getPassword()
  {
    return password;
  }



  /**
   * setter for the password property.
   * 
   * @param password
   *          the password to set
   */
  public void setPassword(String password)
  {
    this.password = password;
  }

}
